﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Forms;
using System.IO;
using System.Xml.Serialization;
using System.Net;
using System.ComponentModel;
using System.Threading;
using Wintellect.PowerCollections;
using System.Text.RegularExpressions;
using TileDownLoader.Utils;
using TileDownLoader.Kml;
using TileDownLoader.Elevation;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace TileDownLoader
{
    public partial class MainWindow : Window
    {
        private BackgroundWorker m_kmlWorker = null;

        #region kml supporting classes
        class KmlThreadArg
        {
            private string selectedPath;

            public string SelectedPath
            {
                get { return selectedPath; }

            }
            private string kmlName;

            public string KmlName
            {
                get { return kmlName; }
            }
            public KmlThreadArg(string selectedPath, string kmlName)
            {
                this.selectedPath = selectedPath;
                this.kmlName = kmlName;
            }
        }

        private static System.Drawing.Imaging.ImageCodecInfo GetEncoderInfo(String mimeType)
        {
            int j;
            System.Drawing.Imaging.ImageCodecInfo[] encoders;
            encoders = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
            for (j = 0; j < encoders.Length; ++j)
            {
                if (encoders[j].MimeType == mimeType)
                    return encoders[j];
            }
            return null;
        }

        #endregion

        private void bttnBrowseInputfolder_Click(object sender, RoutedEventArgs e)
        {
            FolderBrowserDialog browseDialog = new FolderBrowserDialog();
            //browseDialog.RootFolder = Environment.SpecialFolder.MyComputer;
            if (browseDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                txtInputFolder.Text = browseDialog.SelectedPath;
            }
        }

        private void bttnGenerateKml_Click(object sender, RoutedEventArgs e)
        {
            if ((!string.IsNullOrWhiteSpace(txtKMLName.Text)) &&
               (!string.IsNullOrWhiteSpace(txtInputFolder.Text)) && Directory.Exists(txtInputFolder.Text))
            {
                m_kmlWorker = new BackgroundWorker();

                m_kmlWorker.WorkerReportsProgress = true;
                m_kmlWorker.WorkerSupportsCancellation = true;

                m_kmlWorker.DoWork += m_kmlWorker_DoWork;
                m_kmlWorker.ProgressChanged += m_kmlWorker_ProgressChanged;
                m_kmlWorker.RunWorkerCompleted += m_kmlWorker_RunWorkerCompleted;
                m_kmlWorker.RunWorkerAsync(new KmlThreadArg(txtInputFolder.Text, txtKMLName.Text));
            }
            else
            {
                System.Windows.MessageBox.Show("please enter valid kml name and tiles folder.");
            }
        }

        void m_kmlWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                System.Windows.MessageBox.Show("User cancel completed.");
            }
            else if (e.Error != null)
            {
                System.Windows.MessageBox.Show(String.Format("Error:{0}.", e.Error.Message));
            }
            else
            {
                if ((bool)e.Result == true)
                {
                    System.Windows.MessageBox.Show("Kml Generation Completed Successfully.");
                }
                else
                {
                    System.Windows.MessageBox.Show("Kml Generation failed.");
                }
            }
        }

        void m_kmlWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            lblKMZStatus.Content = e.UserState;
            progressKml.Value = e.ProgressPercentage;
        }

        void m_kmlWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            KmlThreadArg arg = e.Argument as KmlThreadArg;
            e.Result = false;

            MultiDictionary<string, ImageInfo> filesGrouped4x4 = new MultiDictionary<string, ImageInfo>(false);

            #region Calc Total final 1024x1024 tiles
            {
                List<ImageInfo> allFiles = new List<ImageInfo>();
                List<string> rows = new List<string>();
                List<string> columns = new List<string>();

                Regex filePattern = new Regex(@"Img_(?<Row>\d+)_(?<Column>\d+)_(?<Zoom>\d+)\.*", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline);

                #region Build Rectangle of Image List
                Parallel.ForEach<string>(Directory.GetFiles(arg.SelectedPath, "*.*", SearchOption.TopDirectoryOnly), item =>
                {
                    Match match = filePattern.Match(item);
                    if (match.Success)
                    {
                        string row = match.Groups["Row"].Value;
                        string column = match.Groups["Column"].Value;

                        lock (allFiles)
                        {
                            allFiles.Add(new ImageInfo(item, row, column, match.Groups["Zoom"].Value));

                            if (!rows.Contains(row))
                            {
                                rows.Add(row);
                            }

                            if (!columns.Contains(column))
                            {
                                columns.Add(column);
                            }
                        }
                    }
                });

                rows.Sort();
                columns.Sort();
                allFiles.Sort((item1, item2) => { return item1.Path.CompareTo(item2.Path); });
                #endregion

                #region Calc total number of final jpegs
                //Calculate How many 1024x1024 bitmaps can be build from above full map
                int totalRowRun = 0;
                int totalColumnRun = 0;

                if (rows.Count > 0)
                {
                    totalRowRun = (int)rows.Count / 4;
                    if (rows.Count % 4 > 0)
                    {
                        totalRowRun++;
                    }
                }

                if (columns.Count > 0)
                {

                    totalColumnRun = (int)columns.Count / 4;
                    if (columns.Count % 4 > 0)
                    {
                        totalColumnRun++;
                    }
                }
                #endregion

                #region Build 4x4 Sets
                string rowKey;
                string columnKey;

                string _4x4Key;

                for (int indexRow = 0; indexRow < totalRowRun; indexRow++)
                {
                    int actualStartRow = indexRow * 4;
                    string rowStartKey = rows[actualStartRow];

                    for (int indexX = actualStartRow; indexX < rows.Count && indexX < actualStartRow + 4; indexX++)
                    {
                        rowKey = rows[indexX];

                        for (int indexColumn = 0; indexColumn < totalColumnRun; indexColumn++)
                        {
                            int actualStartColumn = indexColumn * 4;
                            string startColumnKey = columns[actualStartColumn];
                            _4x4Key = String.Format("{0}x{1}", rowStartKey, startColumnKey);


                            for (int indexY = actualStartColumn; indexY < columns.Count && indexY < actualStartColumn + 4; indexY++)
                            {
                                columnKey = columns[indexY];
                                ImageInfo data = allFiles.GetData<ImageInfo>(rowKey, columnKey);

                                filesGrouped4x4.Add(_4x4Key, data);
                            }
                        }
                    }
                }
                #endregion
            }
            #endregion

            #region Build final tiles
            {
                #region Build tiles
                string kmlFolder = String.Format("{0}\\{1}\\", arg.SelectedPath, arg.KmlName);

                if (Directory.Exists(kmlFolder))
                {
                    System.Windows.MessageBox.Show("Folder already exits, select different folder");
                    return;
                }

                Directory.CreateDirectory(kmlFolder);

                List<GroundOverlayType> overlays = new List<GroundOverlayType>();
                int index = 1;
                
                if (this.KmlWorker.CancellationPending)
                {
                    return;
                }

                ParallelOptions options = new ParallelOptions();

                options.MaxDegreeOfParallelism = 4;

                double progressCount = filesGrouped4x4.Keys.Count;

                Parallel.ForEach<string>(filesGrouped4x4.Keys, options,imgkey =>
                {
                    lock (this.KmlWorker)
                    {
                        this.KmlWorker.ReportProgress((int)(((double)index / progressCount) * 100.0), string.Format("Converted {0} of {1} Tiles", index, filesGrouped4x4.Keys.Count));
                    }

                    #region Build kml data

                    ICollection<ImageInfo> groupedImageInfo;

                    lock (filesGrouped4x4)
                    {
                        groupedImageInfo = filesGrouped4x4[imgkey];
                    }

                    ImageInfo imageInfo = groupedImageInfo.First<ImageInfo>();

                    string fileName = String.Format("{0}\\Img_{1}_{2}_{3}.jpeg", kmlFolder, imageInfo.Row, imageInfo.Column, imageInfo.Zoom);

                    MultiDictionary<string, ImageInfo> tempData = new MultiDictionary<string, ImageInfo>(false);

                    foreach (ImageInfo item in groupedImageInfo)
                    {
                        tempData.Add(item.Row, item);
                    }

                    int rowCount = tempData.Keys.Count;
                    int columnCount = tempData[tempData.Keys.First<string>()].Count;
                    int imageWidth = rowCount * 256;
                    int imageHeight = columnCount * 256;

                    TileDownLoader.Projection.MercatorProjection projections = new TileDownLoader.Projection.MercatorProjection();

                    TileDownLoader.Projection.Point imgPt = new TileDownLoader.Projection.Point(int.Parse(imageInfo.Row), int.Parse(imageInfo.Column));

                    TileDownLoader.Projection.Point imgXYInVirtualSingleImageStPoint = projections.FromTileXYToPixel(imgPt);

                    TileDownLoader.Projection.Point imgXYInVirtualSingleImageEtPoint = new TileDownLoader.Projection.Point(imgXYInVirtualSingleImageStPoint.X + imageWidth,
                        imgXYInVirtualSingleImageStPoint.Y + imageHeight);

                    TileDownLoader.Projection.Location latlng1 = projections.FromPixelToLatLng(imgXYInVirtualSingleImageStPoint, int.Parse(imageInfo.Zoom));
                    TileDownLoader.Projection.Location latlng2 = projections.FromPixelToLatLng(imgXYInVirtualSingleImageEtPoint, int.Parse(imageInfo.Zoom));

                    GroundOverlayType gndOverlay = new GroundOverlayType();

                    gndOverlay.LatLonBox = new LatLonBoxType();

                    gndOverlay.LatLonBox.north = latlng1.Latitude;
                    gndOverlay.LatLonBox.south = latlng2.Latitude;
                    gndOverlay.LatLonBox.east = latlng2.Longitude;
                    gndOverlay.LatLonBox.west = latlng1.Longitude;

                    gndOverlay.name = System.IO.Path.GetFileNameWithoutExtension(fileName);
                    gndOverlay.color = new byte[] { 0xff, 0xff, 0xff, 0xff };
                    gndOverlay.Icon = new LinkType();
                    gndOverlay.Icon.href = System.IO.Path.GetFileName(fileName);
                    #endregion

                    #region Draw Bitmap
                    System.Drawing.Bitmap stichedBmp = new System.Drawing.Bitmap(imageWidth, imageHeight);
                    System.Drawing.Graphics grp = System.Drawing.Graphics.FromImage(stichedBmp);

                    int rowLoop = 0;
                    int columnLoop = 0;
                    List<string> keysTemp = new List<string>(tempData.Keys);
                    keysTemp.Sort();
                    foreach (string key in keysTemp)
                    {
                        int x = rowLoop * 256;
                        columnLoop = 0;
                        foreach (ImageInfo imgData in tempData[key])
                        {
                            int y = columnLoop * 256;

                            System.Drawing.Bitmap bitmapTemp = new System.Drawing.Bitmap(imgData.Path);

                            grp.DrawImage(bitmapTemp, new System.Drawing.Point(x, y));

                            bitmapTemp.Dispose();

                            columnLoop++;
                        }
                        rowLoop++;
                    }
                    #endregion

                    #region Write Bitmap
                    // EncoderParameter object in the array.

                    System.Drawing.Imaging.ImageCodecInfo myImageCodecInfo = GetEncoderInfo("image/jpeg");

                    System.Drawing.Imaging.EncoderParameters myEncoderParameters = new System.Drawing.Imaging.EncoderParameters(1);

                    System.Drawing.Imaging.EncoderParameter myEncoderParameter = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 70L);
                    myEncoderParameters.Param[0] = myEncoderParameter;

                    stichedBmp.Save(fileName, myImageCodecInfo, myEncoderParameters);
                    
                    myEncoderParameter.Dispose();
                    myEncoderParameters.Dispose();

                    grp.Dispose();
                    stichedBmp.Dispose();

                    lock (overlays)
                    {
                        overlays.Add(gndOverlay);
                    }
                    #endregion

                    Interlocked.Increment(ref index);
                });
                #endregion

                #region Build kml file

                overlays.Sort((item1,item2) => { return item1.name.CompareTo(item2.name); });

                List<List<GroundOverlayType>> splitedOverlays = new List<List<GroundOverlayType>>();

                for (int localIndex = 0; localIndex < overlays.Count; localIndex += 99)
                {
                    int lastData = (localIndex + 99) < overlays.Count ? localIndex + 99 : overlays.Count;

                    List<GroundOverlayType> tempOverlays = new List<GroundOverlayType>();

                    for (int subIndex = localIndex; subIndex < lastData; subIndex++)
                    {
                        tempOverlays.Add(overlays[subIndex]);
                    }

                    splitedOverlays.Add(tempOverlays);
                }

                int fileCount = 1;

                foreach (List<GroundOverlayType> item in splitedOverlays)
                {
                    GenerateKmlFile(arg, kmlFolder, item, fileCount);
                    fileCount++;
                }
                #endregion
            }
            #endregion

            this.KmlWorker.ReportProgress(100, string.Format("Converted {0} of {1} Tiles", filesGrouped4x4.Keys.Count, filesGrouped4x4.Keys.Count));

            e.Result = true;
        }

        private static void GenerateKmlFile(KmlThreadArg arg, string kmlFolder, List<GroundOverlayType> fileOverlays, int kmlfileCount)
        {
            FolderType folder = new FolderType();

            folder.name = String.Format("{0}_{1}", arg.KmlName, kmlfileCount);

            folder.Items1 = fileOverlays.ToArray();

            KmlType kml = new KmlType();

            kml.Item = folder;

            string fileNameKmz = String.Format("{0}\\{1}{2}.kml", kmlFolder, arg.KmlName, kmlfileCount);

            using (FileStream fs = File.Create(fileNameKmz))
            {
                XmlSerializer ser = new XmlSerializer(typeof(KmlType));

                ser.Serialize(fs, kml);
            }
        }

        private void bttnCancelKML_Click(object sender, RoutedEventArgs e)
        {
            this.KmlWorker.CancelAsync();
        }
    }
}
